package com.utils;

import com.config.JksConfiguration;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.util.io.pem.PemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.Charset;
import java.security.Key;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 相关证书，私钥等操作工具类
 * @author zyf
 */
public class CertUtil {

    private static Logger logger = LoggerFactory.getLogger(CertUtil.class);

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 从jksConfig中读取获取cert、privateKey、publicKey等
     *
     * @param jksConfiguration
     * @return
     */
    public static List getCertFromJKS(JksConfiguration jksConfiguration) {

        PropertiesConfiguration jksConfig = jksConfiguration.getJksConfig();

        String jksPath = jksConfig.getString("path");
        String jksName = jksConfig.getString("name");
        String jksAlias = jksConfig.getString("alias");
        String jksPasswd = jksConfig.getString("password");

        if (jksPath == null || jksPath.isEmpty()) {
            logger.info("jksPath为空，将使用jksName从classpath下加载jks");
            InputStream jksStream = CertUtil.class.getClassLoader().getResourceAsStream(jksName);
            List list = CertUtil.getCertFromJKS(jksStream, jksPasswd, jksAlias);
            return list;
        } else {
            logger.debug("jksPath: {}", jksPath);
            List list = CertUtil.getCertFromJKS(new File(jksPath), jksPasswd, jksAlias);
            return list;
        }
    }

    /**
     *
     * 通过配置信息获取证书（动态加载）
     * list(0) 为cert； list(1) 为privateKey； list(2) 为publicKey
     * @param jks_file  jks文件路径
     * @param password  密码
     * @param alias     别名
     * @return List（list(0) 为cert； list(1) 为privateKey； list(2) 为publicKey）
     */
    public static List getCertFromJKS(File jks_file, String password, String alias) {
        try {
            FileInputStream fis = new FileInputStream(jks_file);
            List list = getCertFromJKS(fis, password, alias);
            return list;
        } catch (Exception e) {
            logger.error("从JKS文件中获取cert/key异常：{}", e.getMessage(), e);
        }
        return null;
    }

    /**
     * 通过配置信息获取证书（动态加载）
     * list(0) 为cert； list(1) 为privateKey； list(2) 为publicKey
     *
     * @param jks_Stream jks输入流
     * @param password   密码
     * @param alias      别名
     * @return List（list(0) 为cert； list(1) 为privateKey； list(2) 为publicKey）
     */
    public static List getCertFromJKS(InputStream jks_Stream, String password, String alias) {
        try {
            KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
            char[] pwd = password.toCharArray();
            store.load(jks_Stream, pwd);
            Key sk = store.getKey(alias, pwd);
            Certificate cert = store.getCertificate(alias);
            PrivateKey privateKey = (PrivateKey) sk;
            List list = new ArrayList();
            list.add(cert);
            list.add(privateKey);
            list.add(cert.getPublicKey());
            return list;
        } catch (Exception e) {
            logger.error("从JKS文件中获取cert/key异常：{}", e.getMessage(), e);
        } finally {
            // close the input stream
            try {
                if (jks_Stream != null) {
                    jks_Stream.close();
                }
            } catch (IOException e) {
                logger.error("Could not close input stream", e);
            }
        }
        return null;
    }

    /**
     *
     * 获取本地证书，得到证书类和其序列化的字节序列
     * {@link SerializeUtil#serialize} {@link Base64#encodeBase64}
     * @param certPath 证书路径
     * @return 证书字节序列，list(0)x509Cert, list(1){@code SerializeUtil.serialize(x509Cert);} list(2) {@code Base64.encodeBase64(arrayCert)}
     */
    public static List loadCertByPath(String certPath) {
        try {
            CertificateFactory certF = CertificateFactory.getInstance("X.509");
            FileInputStream fileInputStream = new FileInputStream(certPath);
            Certificate x509Cert = certF.generateCertificate(fileInputStream);
            byte[] arrayCert = SerializeUtil.serialize(x509Cert);
            List list = new ArrayList();
            list.add(x509Cert);
            list.add(arrayCert);
            list.add(Base64.encodeBase64(arrayCert));
            return list;
        } catch (Exception e) {
            logger.error("根据certPath中构造证书异常：{}", e.getMessage(), e);
        }
        return null;
    }

    /**
     *
     * 私钥格式为pkcs8
     * @param pemPathOrString 文件路径,或者字符串(包含有私钥与证书的PEMString)
     * @param pass            私钥密码
     * @return List（list(0) 为cert； list(1) 为privateKey； list(2) 为publicKey）
     * @throws Exception
     */
    public static List generatePrikeyCertWithPem(String pemPathOrString, String pass) throws Exception {
        String priCertPemString = pemPathOrString;
        File pemFile = new File(pemPathOrString);
        if (pemFile.isFile()) {
            priCertPemString = FileUtils.readFileToString(pemFile,  Charset.forName("UTF-8"));
        }
//        String regex = "-+BEGIN.+PRIVATE\\s+KEY-+[\\w+=\\/\\r\\n]*-*END.+PRIVATE\\s+KEY-+";
        String priRegex = "-+BEGIN.+PRIVATE\\s+KEY-+[\\s\\S]+-+END.+PRIVATE\\s+KEY-+";
        String cerRegex = "-+BEGIN\\s+CERTIFICATE-+[\\s\\S]+-+END\\s+CERTIFICATE-+";
        Pattern pattern = Pattern.compile(priRegex);
        Matcher mt = pattern.matcher(priCertPemString);
        String priPem = "";
        String cerPem = "";
        if (mt.find()) {
            priPem = mt.group();
        }
        pattern = Pattern.compile(cerRegex);
        mt = pattern.matcher(priCertPemString);
        if (mt.find()) {
            cerPem = mt.group();
        }
        PrivateKey privateKey = KeyUtil.generatePrikeyWithPem(priPem, pass);
        Certificate certificate = generateCerWithPem(cerPem);
        List list = new ArrayList();
        list.add(certificate);
        list.add(privateKey);
        list.add(certificate.getPublicKey());
        return list;
    }

    /**
     * 根据证书pem字符串，构造证书，construct certificate by pemString
     * @param cerPem        cerPem是读取pem证书文件得到的字符串
     * @return              Certificate
     * @throws Exception
     */
    public static Certificate generateCerWithPem (String cerPem) throws Exception {
        PEMParser pemParser = new PEMParser(new StringReader(cerPem));
        Object object = pemParser.readObject();
        X509CertificateHolder x509Holder = (X509CertificateHolder)object;
        Certificate certificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(x509Holder);
        return certificate;
    }

    /**
     * 根据证书pem字符串，构造证书，construct certificate by pemString
     * @param certPem       cerPem是读取pem证书文件得到的字符串
     * @return              X509Certificate
     * @throws Exception
     */
    public static X509Certificate generateX509CertWithPem(String certPem) throws Exception{
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        PemReader pemReader = new PemReader(new StringReader(certPem));
        byte[] certByte = pemReader.readPemObject().getContent();
        X509Certificate x509Cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(certByte));
        return x509Cert;
    }

    /**
     * 根据证书获取短地址
     * @param certificate
     * @return
     * @throws Exception
     */
    public static String getAddrByCertifcate(X509Certificate certificate) throws Exception{
        return KeyUtil.getAddrByPublicKey(certificate.getPublicKey());
    }
}
